import math
from DataDistribution.fileMethod import FileExtend
import os
import gevent

from Extend.GrandClass import GrandClass
from Extend.Log import *
from Extend.NoSql import Heart, Distribution


# 文件分发的实体类
class DistributorModel:
    def __init__(self, server, weight, path):
        # 服务器地址，用作特征
        self.__server = server
        # 服务器权重
        self.__weight = weight
        # 服务器存放文件的路径
        self.__path = os.path.join(r'\\' + server, path)
        # 服务器的文件操作方法
        self.__file = FileExtend(self.__path)
        # 服务器的当前存在文件数
        self.__file_count = 0

    def startMonitor(self):
        MonitorD.getServerFileCount(self)

    @property
    def weight(self):
        return self.__weight

    def distributionFile(self, file):
        self.__file.moveFile(file)

    @property
    def server(self):
        return self.__server

    @property
    def path(self):
        return self.__path

    def __getfileCount(self):
        return self.__file_count

    def __setfileCount(self, count):
        self.__file_count = count

    FileCount = property(__getfileCount, __setfileCount)

    def __str__(self):
        return self.__server + '  ' + str(self.FileCount)


class Distributor(GrandClass):
    """分发器主逻辑，用来分配目标服务器地址，需要传入一个列表，列表中包含服务的地址和分发的权重值"""

    def __init__(self, scan_path, server_list: list, ):
        super().__init__()
        self.server_list = []
        self.weight_value = []
        self.offline_server = []
        self.recovery_server = []
        self.path = scan_path
        self.__addServer(server_list)

    def __addServer(self, server_list: list):
        self.serverSorted(server_list)
        for x in server_list:
            self.server_list.append(DistributorModel(x['Server'], x['Weight'], x['DistributePath']))

    def _recoveryServer(self, server):
        for x in self.server_list + self.recovery_server:
            if x is server:
                return
        self.recovery_server.append(server)

    def recoveryServerToRun(self):
        for x in range(-len(self.recovery_server) + 1, 1):
            server = self.recovery_server.pop(abs(x))
            self.server_list.append(server)
            LogMethod.addLog([3, "服务器:%s 已经恢复通讯" % server.server, ""])
        self.server_list.sort(key=(lambda s: s.weight), reverse=True)
        self.algorithm()

    def addServer(self, server_list: list):
        self.clearServeList()
        self.__addServer(server_list)

    # 循环主逻辑
    def startMonitors(self):
        self.algorithm()
        while Heart.getMeHeart(self.__class__.__name__, ):
            for i in FileExtend.getFileByMtime(self.path):
                super().startMonitors()
                if len(self.recovery_server) > 0:
                    # print('有恢复的！')
                    self.recoveryServerToRun()
                    break
                for x in self.server_list:
                    try:
                        x.startMonitor()
                    except Exception as err:
                        self.removeServer(x, err)
                self.assignFiles(i)
                gevent.sleep(0.5)  # 工作太快协程会不检测服务器状态
            time.sleep(1)
        Heart.setMeStat(self.__class__.__name__, False)

    def removeServer(self, server, error, permission_deny=False):
        for i in range(len(self.server_list)):
            if self.server_list[i] is server:
                self.server_list.pop(i)
                if i == 0:
                    self.weight_value.pop(i)
                else:
                    self.weight_value.pop(i - 1)
                break
        if permission_deny:
            LogMethod.addLog([2, "服务器:%s 无法写入" % server.server, str(error)])
            gevent.spawn(MonitorD.writePermissionDetection, server, self._recoveryServer)
        else:
            LogMethod.addLog([1, "服务器:%s 无法连接" % server.server, str(error)])
            gevent.spawn(MonitorD.heartbeatDetection, server, self._recoveryServer)

    def clearServeList(self):
        for i in range(len(self.server_list)):
            server = self.server_list.pop(i)
            del server

    # 分配文件
    def assignFiles(self, file_name):
        file = os.path.join(self.path, file_name)
        # 如果只有一台服务器就只分配权重的数量，减少服务器压力
        if len(self.server_list) == 1:
            if self.server_list[0].FileCount < self.server_list[0].weight:
                self.moveFile(self.server_list[0], file)
            else:
                time.sleep(1)
            return
        for x in self.server_list:
            if x.FileCount == 0:
                if self.moveFile(x, file):
                    break
        else:
            for x in range(-len(self.weight_value), 0):
                index = abs(x)
                #  服务器当前文件数 = 向上取整(当前服务器权重比 * 向上取整(权重最大服务器当前文件数 / 权重最大服务器权重值))
                if self.judgeAlgorithm(index):
                    if self.moveFile(self.server_list[index], file):
                        break
            else:
                self.moveFile(self.server_list[0], file)

    # 单独列出来只是为了方便检测是否异常
    def moveFile(self, server: DistributorModel, file):
        # print('当前活动服务器为:{},权重比分别是:{}'.format(','.join([t.server for t in self.server_list]),
        #                                      '、'.join([str(x) for x in self.weight_value])))
        try:
            server.distributionFile(file)
            Distribution.addLog(os.path.basename(file), server.server)
            return True
        except Exception as error:
            # print(str(error) + server.server)
            self.removeServer(server, error, True)
            return False

    # 设定权重比
    def algorithm(self):
        self.weight_value.clear()
        loop = 0
        weight = self.server_list[0].weight
        for x in self.server_list:
            if loop > 0:
                self.weight_value.append(weight / x.weight)
            loop += 1

    # 判断权重
    def judgeAlgorithm(self, index):
        max_weight_count = self.server_list[0].FileCount
        this_count = self.server_list[index].FileCount
        #  服务器当前文件数 = 向上取整(当前服务器权重比 * 向上取整(权重最大服务器当前文件数 / 权重最大服务器权重值))
        w_weight = math.ceil(self.weight_value[index - 1] * this_count)
        if this_count == 1:
            if max_weight_count < self.weight_value[index - 1] * 2:
                return False
            else:
                return True
        else:
            if max_weight_count >= w_weight:
                return True
            else:
                return False

    @staticmethod
    def serverSorted(server_list: list):
        server_list.sort(key=(lambda element: element['Weight']), reverse=True)


# 监测服务器类
class MonitorD:

    @staticmethod
    def getServerFileCount(obj: DistributorModel, loop=0):
        file_count = 0
        try:
            for f in os.listdir(obj.path):
                if os.path.isfile(os.path.join(obj.path, f)):
                    file_count += 1
            if file_count != obj.FileCount:
                obj.FileCount = file_count
        except OSError as error:
            if error.args[0] == 22 and loop < 3:
                time.sleep(3)
                MonitorD.getServerFileCount(obj, loop + 1)
            else:
                raise error

    @staticmethod
    def heartbeatDetection(obj: DistributorModel, return_func, loop=0):
        while True:
            try:
                os.listdir(obj.path)
                break
            except Exception as e:
                loop += 1
                if loop <= 50:
                    time.sleep(5)
                elif loop <= 100:
                    time.sleep(10)
                else:
                    time.sleep(20)
        return_func(obj)

    @staticmethod
    def writePermissionDetection(obj: DistributorModel, return_func, loop=0):
        while True:
            try:
                temp_dir = os.path.join(obj.path, 'AuthorityDetection')
                os.makedirs(temp_dir)
                os.removedirs(temp_dir)
                break
            except Exception as e:
                loop += 1
                if loop <= 50:
                    time.sleep(5)
                elif loop <= 100:
                    time.sleep(10)
                else:
                    time.sleep(20)
        return_func(obj)


if __name__ == '__main__':
    servers = [{'Server': "172.16.200.147", 'Weight': 5, 'DistributePath': "testdir"},
               {'Server': "192.168.232.131", 'Weight': 2, 'DistributePath': "downloadfile"},
               {'Server': "gqt", 'Weight': 8, 'DistributePath': "测试"}]
    dt = Distributor(r'F:\CloudMusic', servers)
    dt.start()
